/*
 * Copyright (c) 2011, Nikolaus Moll
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * * Neither the name of the gradecalculator.net nor the
 *   names of its contributors may be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */
package net.gradecalculator.gui.qt.editor;

import java.io.File;

import net.gradecalculator.core.GCCore;
import net.gradecalculator.core.interfaces.ICourse;
import net.gradecalculator.core.interfaces.IElectiveCourse;
import net.gradecalculator.core.interfaces.IElectiveCoursesArea;
import net.gradecalculator.core.interfaces.IElectiveModulesArea;
import net.gradecalculator.core.interfaces.IModule;
import net.gradecalculator.core.interfaces.IModuleList;
import net.gradecalculator.core.interfaces.IModuleListItem;
import net.gradecalculator.core.interfaces.IStudyRegulations;
import net.gradecalculator.gui.qt.common.dialogs.AbstractDialog;
import net.gradecalculator.gui.qt.common.dialogs.CommonDialogs;
import net.gradecalculator.gui.qt.common.interfaces.IContextMenuListener;
import net.gradecalculator.gui.qt.common.interfaces.IMutliColumnTreeEventListener;
import net.gradecalculator.gui.qt.common.widgets.MultiColumnTree;
import net.gradecalculator.gui.qt.common.widgets.TreeIndex;
import net.gradecalculator.gui.qt.editor.dialogs.CourseDialog;
import net.gradecalculator.gui.qt.editor.dialogs.FileRegulationsDialog;
import net.gradecalculator.gui.qt.editor.dialogs.IndividualModuleDialog;
import net.gradecalculator.gui.qt.editor.dialogs.ModuleListDialog;
import net.gradecalculator.gui.qt.editor.dialogs.SelectableModuleListsDialog;
import net.gradecalculator.gui.qt.editor.dialogs.StudyRegulationsDescriptionDialog;
import net.gradecalculator.gui.qt.editor.jowidgets.JoModuleDialog;
import net.gradecalculator.gui.qt.editor.models.RegulationTreeModel;
import net.gradecalculator.plugin.core.interfaces.IRegulationsPlugin;
import net.gradecalculator.plugin.loader.exceptions.PluginException;
import net.miblounge.jombi.impl.widgets.internal.FrameWrapper;

import org.jowidgets.api.toolkit.Toolkit;
import org.jowidgets.api.widgets.IInputDialog;
import org.jowidgets.common.widgets.factory.IGenericWidgetFactory;

import com.trolltech.qt.core.QPoint;
import com.trolltech.qt.core.QSize;
import com.trolltech.qt.gui.QAction;
import com.trolltech.qt.gui.QApplication;
import com.trolltech.qt.gui.QCloseEvent;
import com.trolltech.qt.gui.QDialog;
import com.trolltech.qt.gui.QMainWindow;
import com.trolltech.qt.gui.QMenu;
import com.trolltech.qt.gui.QMessageBox;
import com.trolltech.qt.gui.QToolBar;
import com.trolltech.qt.gui.QWidget;

public class RegulationsEditor extends QMainWindow implements IMutliColumnTreeEventListener, IContextMenuListener {
	public enum ChangedDialogResult { CONTINUE, CANCEL };

	private MultiColumnTree srdx;
	private IStudyRegulations regulationDescription = null;
	private String openedFile = null;
	private int srdxHash = 0;

	private QAction actNewDescriptionFile;
	
	private QAction actNewModule;
	private QAction actNewIndividualModule;
	private QAction actNewSelectableModuleLists;
	private QAction actNewModuleList;
	private QAction actNewCourse;
	private QAction actNewIndividualCourse;

	private QAction actEdit;
	private QAction actRemove;
	
	private RegulationTreeModel model;
	private TreeIndex currentTreeIndex;
	private Object currentTreeItem;
	private final FrameWrapper<QMainWindow> windowWrapper;
	
	public RegulationsEditor(final QWidget parent){
        super(parent);
		final IGenericWidgetFactory fab = Toolkit.getWidgetFactory();
		windowWrapper = new FrameWrapper<QMainWindow>(fab, this);

        createStatusBar();
        createActions();
        createToolBars();
        createMenus();

        srdx = new MultiColumnTree(this);
        srdx.addTreeEventListener(this);
        srdx.addContextMenuListener(this);
        setCentralWidget(srdx);
        
        
        
        // TODO: REMOVE TEST CODE
        final File file = new File("./SS2008.srdx");
        if (file.exists()) {
        	openFile(file.getAbsolutePath(), GCCore.getReglationsPlugins().get(0));
        } else {
        	setOpenedFile(null);
        }
	}
	
	protected void setOpenedFile(final String filename)
	{
        String title = tr("Grade Calculator - SRDX-Editor") + " " + ProgramDescription.getVersion();
        if (filename != null) {
        	openedFile = filename;
        	String usedname = filename;
        	usedname = usedname.replace("\\", "/");
        	final int index = usedname.lastIndexOf('/');
        	if (index >= 0) {
        		usedname = usedname.substring(index + 1);
        	}
        	title += " - " + usedname;
        }
        
        if (regulationDescription != null) {
			srdxHash = regulationDescription.hashCode();
        } else {
			srdxHash = 0;	// reset hash
        }
		this.setWindowTitle(title);
        updateActions();
	}

	protected void createActions()
	{
		actNewDescriptionFile = createAction(tr("New description file"), "actionNewDescriptionFile()", tr("Create a new description file"));
		actNewModule = createAction(tr("New module"), "actionAddModule()", tr("Adds a new module"));
		actNewIndividualModule = createAction(tr("New individual module"), "actionAddIndividualModule()", tr("Adds an individual module"));
		actNewSelectableModuleLists = createAction(tr("New selectable modules"), "actionAddSelectableModuleLists()", tr("Adds selectable modules"));
		actNewModuleList = createAction(tr("New module list"), "actionAddModuleList()", tr("Adds a new module list"));
		actNewCourse = createAction(tr("New course"), "actionAddCourse()", tr("Adds a new course"));
		actNewIndividualCourse = createAction(tr("New individual course"), "actionAddIndividualCourse()", tr("Adds a new individual course"));

		actEdit = createAction(tr("Edit"), "actionEdit()", tr("Edits an item"));
		actRemove = createAction(tr("Remove"), "actionRemove()", tr("Removes an item"));
	}
	
	protected void updateActions()
	{
		actNewModule.setEnabled(model != null);
		actNewIndividualModule.setEnabled(model != null);
		actNewSelectableModuleLists.setEnabled(model != null);
		actNewModuleList.setEnabled(getParent(currentTreeIndex, IElectiveModulesArea.class)!= null);
		actNewCourse.setEnabled(getParent(currentTreeIndex, IModule.class)!= null);
		actNewIndividualCourse.setEnabled(getParent(currentTreeIndex, IModule.class)!= null);
	}
	
	private QAction createAction(final String text, final String action, final String statustext)
	{
		final QAction result = new QAction(text, this);
	    result.triggered.connect(this, action);
	    result.setStatusTip(statustext);
	    return result;
	}
	
	private void createStatusBar()
    {
        statusBar().showMessage(tr("Ready"));
    }
	
	protected void actionNewDescriptionFile()
	{
		if (checkClose() == ChangedDialogResult.CANCEL) {
			return;
		}
		
		final IStudyRegulations reg = GCCore.getFactory().createStudyRegulations(); 
		final AbstractDialog dialog = new StudyRegulationsDescriptionDialog(this, reg);

		if (dialog.exec() == QDialog.DialogCode.Accepted.ordinal()) {
			dialog.apply();
			regulationDescription = reg;
			model = new RegulationTreeModel(this, regulationDescription);
			srdx.setModel(model);
			setOpenedFile(null);
		}
	}
	
    public String getFilename(final String title)
    {
    	
//        final String filter = parent.tr(filetype) + " (*." + extension + ")";
//        final String result = QFileDialog.getOpenFileName(parent, parent.tr(title), lastDirectory,
//                new QFileDialog.Filter(filter));
//
//        if ((result == null) || (result.equals(""))) {
//            return null;
//        }
//
//        final File file = new File(result);
//        lastDirectory = file.getParentFile().getAbsolutePath();

//        return result;
    	return null;
    }

	private void openFile(final String filename, final IRegulationsPlugin plugin)
	{
		// TODO
		try {
			regulationDescription = plugin.readStudyRegulations(filename);
			model = new RegulationTreeModel(this, regulationDescription);
			srdx.setModel(model);
			setOpenedFile(filename);
		}
		catch (final PluginException e)
		{
			QMessageBox.critical(this, "Error", e.toString());
		}
	}

	protected void actionOpen()
	{
		if (checkClose() == ChangedDialogResult.CANCEL) {
			return;
		}

    	final FileRegulationsDialog fileDialog = new FileRegulationsDialog(this, "Open regulations file");
    	final int dialogResult = fileDialog.exec();
    	if (dialogResult != QDialog.DialogCode.Accepted.ordinal()) {
    		return;
    	}
	
		openFile(fileDialog.selectedFiles().get(0), fileDialog.getPlugin());
	}
		
	protected String getSaveAsFilename()
	{
		final String result = CommonDialogs.getOpenFilename(this, "Save regulations file as", "Study regulations description", "srdx");
		
		if ((result == null) || (result.equals(""))) {
			return null;
		}
		
		return result;
	}

	
	protected boolean saveSPODescription(final String filename)
	{
		if (regulationDescription == null) {
			return true;
		}
		
		String currentFile = filename;
		if (currentFile == null) {
			currentFile = getSaveAsFilename();
			if (currentFile == null) {
				return false;
			}
		}
		
		try {
			// TODO
			//regulationDescription.saveXML(currentFile);
			setOpenedFile(currentFile);
	        updateActions();
			return true;
		}
		catch (final PluginException e)
		{
			QMessageBox.critical(this, "Error", e.getMessage());
			return false;
		}
	}

	protected boolean isChanged()
	{
		if ((regulationDescription == null) || (srdxHash == regulationDescription.hashCode())) {
			return false;
		}
		return true;
	}
	
	protected ChangedDialogResult checkClose()
	{
		if (!isChanged()) {
			return ChangedDialogResult.CONTINUE;
		}
		
		final QMessageBox.StandardButtons buttons = new QMessageBox.StandardButtons();
        buttons.set(QMessageBox.StandardButton.Save);
        buttons.set(QMessageBox.StandardButton.Discard);
        buttons.set(QMessageBox.StandardButton.Cancel);
        final QMessageBox.StandardButton ret = QMessageBox.warning(this, tr("Grade Calculator"),
                          tr("Regulations data has been modified.\n"
                             + "Do you want to save your changes?"),
                             buttons, QMessageBox.StandardButton.Save);	
        
        if (ret == QMessageBox.StandardButton.Cancel) {
        	return ChangedDialogResult.CANCEL; 
        } 
        
        if (ret == QMessageBox.StandardButton.Save) {
        	if (!saveSPODescription(openedFile)) {
        		return ChangedDialogResult.CANCEL;
        	}
        }

        return ChangedDialogResult.CONTINUE;
	}


	protected void actionSave()
	{
		saveSPODescription(openedFile);
	}
	
	protected void actionSaveAs()
	{
		saveSPODescription(null);
	}
	
	protected void actionExit()
	{
		close();
	}
	
	private void createToolBars()
    {
		final QToolBar toolbar = addToolBar(tr("Toolbar"));
		toolbar.addAction(actNewModule);
		toolbar.addAction(actNewIndividualModule);
		toolbar.addAction(actNewSelectableModuleLists);
		toolbar.addAction(actNewModuleList);
		toolbar.addAction(actNewCourse);
		toolbar.addAction(actNewIndividualCourse);
    }
	

	 
    private void createMenus()
    {
    	final QMenu menuFile = menuBar().addMenu(tr("&File"));
    	menuFile.addAction(actNewDescriptionFile);
    	menuFile.addAction(createAction(tr("&Open"), "actionOpen()", tr("Open a regulations file")));
    	menuFile.addAction(createAction(tr("&Save"), "actionSave()", tr("Saves the current file")));
    	menuFile.addAction(createAction(tr("Save &as"), "actionSaveAs()", tr("Saves the current file with a new name")));
    	menuFile.addSeparator();
    	menuFile.addAction(createAction(tr("&Exit"), "actionExit()", tr("Exits the programm")));

    	final QMenu menuNew = menuBar().addMenu(tr("&New"));
    	menuNew.addAction(actNewModule);
    	menuNew.addAction(actNewIndividualModule);
    	menuNew.addAction(actNewSelectableModuleLists);
    	menuNew.addAction(actNewModuleList);
    	menuNew.addAction(actNewCourse);
    	menuNew.addAction(actNewIndividualCourse);

//		  menuHelp = menuBar().addMenu(tr("&Help"));
//        menuHelp.addAction(actAbout);
//        menuHelp.addAction(actAboutQtJambi); 
    }    

    @Override
    protected void closeEvent(final QCloseEvent event)
    {
        if (checkClose() == ChangedDialogResult.CANCEL) {
        	event.ignore();
	    } else {
	    	event.accept();
	    }
    }    
    
    
	@Override
	public void treeEditRequestEvent(final TreeIndex index, final int column, final Object data) {
		startEdit(index, data);
	}

	@Override
	public void treeSelectionChangedEvent(final TreeIndex index, final Object data) {
		currentTreeIndex = index;
		currentTreeItem = data;
		updateActions();
	}
	
	protected TreeIndex getParent(final TreeIndex index, final Class<?> type)
	{
		TreeIndex current = index;
		while (current != null) {
			final Object data = model.getData(current);
			if ((data != null) && (data.getClass().equals(type))) {
				return current;
			}
			
			current = current.getParent();
		}
		
		return null;
	}

	protected void actionAddModule()
	{
		TreeIndex parent = null;
		if (currentTreeIndex != null) {
			parent = getParent(currentTreeIndex, IModuleList.class);
		}
		
		
		
		final JoModuleDialog<IModule> dialog = new JoModuleDialog<IModule>(windowWrapper, "Module");
		dialog.getInputDialog().setValue(GCCore.getFactory().createModule());
		dialog.getInputDialog().setVisible(true);
		if (dialog.getInputDialog().isOkPressed()) {
			dialog.apply();
			model.addModuleListItem(parent, dialog.getInputDialog().getValue());
		}

//		if (dialog.exec() == QDialog.DialogCode.Accepted.ordinal()) {
//			dialog.apply();
//			model.addModuleListItem(parent, module);
//		}
	}
    
	protected void actionAddCourse()
	{
		TreeIndex parent = null;
		if (currentTreeIndex != null) {
			parent = getParent(currentTreeIndex, IModule.class);
		}
		if (parent == null) {
			return;
		}
		
		final ICourse course = GCCore.getFactory().createCourse();
		final CourseDialog dialog = new CourseDialog(this, regulationDescription, course);

		if (dialog.exec() == QDialog.DialogCode.Accepted.ordinal()) {
			dialog.apply();
			model.addCourse(parent, course);
		}
	}
	
	protected void actionAddIndividualCourse()
	{
		TreeIndex parent = null;
		if (currentTreeIndex != null) {
			parent = getParent(currentTreeIndex, IModule.class);
		}
		if (parent == null) {
			return;
		}
		
		// TODO
		final IElectiveCourse course = GCCore.getFactory().createElectiveCourse();
		final CourseDialog dialog = new CourseDialog(this, regulationDescription, course);

		if (dialog.exec() == QDialog.DialogCode.Accepted.ordinal()) {
			dialog.apply();
			model.addCourse(parent, course);
		}
	}
	
	protected void actionAddIndividualModule()
	{
		TreeIndex parent = null;
		if (currentTreeIndex != null) {
			parent = getParent(currentTreeIndex, IModuleList.class);
		}
		
		final IElectiveCoursesArea module = GCCore.getFactory().createElectiveCoursesArea();
		final IndividualModuleDialog dialog = new IndividualModuleDialog(this, module);

		if (dialog.exec() == QDialog.DialogCode.Accepted.ordinal()) {
			dialog.apply();
			model.addModuleListItem(parent, module);
		}
	}
	
	protected void actionAddSelectableModuleLists()
	{
		TreeIndex parent = null;
		if (currentTreeIndex != null) {
			parent = getParent(currentTreeIndex, IModuleList.class);
		}
		
		final IElectiveModulesArea module = GCCore.getFactory().createElectiveModulesArea();
		final SelectableModuleListsDialog dialog = new SelectableModuleListsDialog(this, module);

		if (dialog.exec() == QDialog.DialogCode.Accepted.ordinal()) {
			dialog.apply();
			model.addModuleListItem(parent, module);
		}
	}
    
	protected void actionAddModuleList()
	{
		TreeIndex parent = null;
		if (currentTreeIndex != null) {
			parent = getParent(currentTreeIndex, IElectiveModulesArea.class);
		}
		
		final IModuleList moduleList = GCCore.getFactory().createModuleList();
		final ModuleListDialog dialog = new ModuleListDialog(this, moduleList);

		if (dialog.exec() == QDialog.DialogCode.Accepted.ordinal()) {
			dialog.apply();
			model.addModuleList(parent, moduleList);
		}
	}

	protected void actionEdit()
	{
		if (currentTreeItem == null) {
			return;
		}
		
		startEdit(currentTreeIndex, currentTreeItem);
	}
	
	protected void actionRemove()
	{
		if (currentTreeItem == null) {
			return;
		}
		
		// currentTreeItem may change within this method, so use a copy of it instead
		final Object item = currentTreeItem;
		
		if (item instanceof ICourse) {
			model.removeCourse(currentTreeIndex, (ICourse)item);
		}

		if (item instanceof IModuleList) {
			model.removeModuleList(currentTreeIndex, (IModuleList)item);
		}

		if (item instanceof IModuleListItem) {
			model.removeModuleListItem(currentTreeIndex, (IModuleListItem)item);
		}

		//			final TaggedTreeWidgetItem<ModuleList> parent = (TaggedTreeWidgetItem<ModuleList>)item.parent();
//			if (parent == null) {
//				// top level element
//				regulationDescription.getModuleList().removeModule(module);
//			} else {
//				parent.getTag().removeModule(module);
//			}
//
//		final QTreeWidgetItem parent = item.parent();
//		if (parent == null) {
//			// top level element
//			for (int i = 0; i < topLevelItemCount(); i++) {
//				if (topLevelItem(i) == item) {
//					takeTopLevelItem(i);
//					break;
//				}
//			}
//		} else {
//			parent.removeChild(item);
//		}
	
	
	}
	
	private void startEdit(final TreeIndex index, final Object data)
	{
		if (data instanceof IModule) {
			final IInputDialog<IModule> dialog = new JoModuleDialog(windowWrapper, "Module").getInputDialog();
			dialog.setValue((IModule)data);
			dialog.setVisible(true);
			System.out.println(dialog.getValue());

			return;
		}

		
		AbstractDialog dialog = null;
		if (data instanceof ICourse) {
			dialog = new CourseDialog(this, regulationDescription, (ICourse)data);
		}
		if (data instanceof IElectiveCoursesArea) {
			dialog = new IndividualModuleDialog(this, (IElectiveCoursesArea)data);
		}
		if (data instanceof IElectiveModulesArea) {
			dialog = new SelectableModuleListsDialog(this, (IElectiveModulesArea)data);
		}
		if (data instanceof IModuleList) {
			dialog = new ModuleListDialog(this, (IModuleList)data);
		}
		
		if (data instanceof IStudyRegulations) {
			dialog = new StudyRegulationsDescriptionDialog(this, (IStudyRegulations)data);
		}
		
		if (dialog != null) {
			if (dialog.exec() == QDialog.DialogCode.Accepted.ordinal()) {
				dialog.apply();
				srdx.recursiveUpdate(index);
			}
		}
	}

	@Override
	public void contextMenuTriggeredEvent(final QPoint position) {
		final QMenu menu = new QMenu(this);
		menu.addAction(actEdit);
		menu.addAction(actRemove);
		menu.addSeparator();

		if (actNewModule.isEnabled()) {
			menu.addAction(actNewModule);
		}
		if (actNewIndividualModule.isEnabled()) {
			menu.addAction(actNewIndividualModule);
		}
		if (actNewSelectableModuleLists.isEnabled()) {
			menu.addAction(actNewSelectableModuleLists);
		}
		if (actNewModuleList.isEnabled()) {
			menu.addAction(actNewModuleList);
		}
		if (actNewCourse.isEnabled()) {
			menu.addAction(actNewCourse);
		}
		if (actNewIndividualCourse.isEnabled()) {
			menu.addAction(actNewIndividualCourse);
		}
      
		menu.exec(position);
	}


    public static void main(final String[] args) {
    	System.out.println(RegulationsEditor.class.getProtectionDomain().getCodeSource().getLocation().getFile());

        QApplication.initialize(args);

        final RegulationsEditor window = new RegulationsEditor(null);
        window.setMinimumSize(new QSize(640, 480));
        window.show();

        QApplication.exec();
    }
}
